﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Diagnostics;
using System.Reflection;
using System.Xml;
using System.Collections.Specialized;
using System.Windows.Forms;
using Microsoft.Win32;

namespace TsChanManLibrary
{
    public enum DvbSPols
    {
        Vertical,
        Horizontal
    }

    public enum DvbSModes
    {
        DvbQPSK,
        TurboQPSK,
        Turbo8PSK,
        Turbo16QAM,
        DCIIComboQPSK,
        DCIIsplitQPSKI,
        DCIIsplitQPSKQ,
        DCIIOQPSK,
        DvbS2
    }

    public enum ChannelType
    {
        DVBSFull,
        DVBS,
        DVBT,
        DVBC,
        ATSC,
        SCTE
    }

    public enum FileExportType
    {
        XML,
        HTML
    }

    public class cChannels
    {
        public List<ChannelBase> currChannels
        { get { return _currChannels; } }

        private List<ChannelBase> _currChannels;
        private ChannelType cType;

        public cChannels(ChannelType ct)
        {
            cType = ct;

            _currChannels = new List<ChannelBase>();

            LoadChannels();
            CheckForOldChannels();
        }

        /// <summary>
        /// Launch the frequency, which belongs to the channel, in TSReader
        /// </summary>
        /// <param name="cn"></param>
        public void LaunchFrequency(ChannelBase cn)
        {
            string args = TsRStartSettings(true) + ParameterasHardware(cn, 0);
            LaunchTSReader(args);
        }

        public void LaunchChannelInVLC(ChannelBase cn, int iVlcOption)
        {
            string args = TsRStartSettings(false) + ParameterasHardware(cn, iVlcOption);
            LaunchTSReader(args);
        }

        public void RecordChannel(ChannelBase cn, RecordingSettings rs)
        {
            string args = TsRStartSettings(true);

            if (rs.bAsProgramStream) { args += "-p "; }
            if (rs.bIncludeSubtitles) { args += "-u "; }

            if (rs.bEntireTS)
            {
                if (rs.bSplitRecording)
                { args += "-A " + rs.iSplitFileSize; }
                else
                { args += "-a"; }
            }
            else
            {
                if (rs.bSplitRecording)
                { args += "-R " + rs.iSplitFileSize; }
                else
                { args += "-r"; }
            }

            args += " " + cn.iProgNo + @" \""" + rs.sRecordingFile + @"\"" " + rs.tsRecordingTime.TotalSeconds + " ";

            args += ParameterasHardware(cn, 0);

            if (rs.bTimedRecording)
            { CreateScheduledTask(args, rs); }
            else
            { LaunchTSReader(args); }
        }

        public void LaunchChannelFromClientData(ChannelBase cn)
        {
            // VLC will start streaming using this command (Command No. 16)
            Registry.SetValue(@"HKEY_CURRENT_USER\Software\COOL.STF\TSReader", "VLCConfigCommand15", cn.VLCCommand);

            // Add the channel if it not already exists
            if (!ChannelExists(cn))
            {
                // Reset details we don't need
                cn.VLCCommand = null;
                cn.iFavorite = 0;
                AddChannel(cn);
                //MessageBox.Show("A new channel named '" + cn.sName + "' has been added into the ch" + 
            }
            LaunchChannelInVLC(cn, 16);
        }

        /// <summary>
        /// Export all details to the frequency (which the channel belongs to) to
        /// XML or HTML (using TSReader)
        /// </summary>
        /// <param name="fet">Type of file</param>
        /// <param name="FileName">Path+Filename</param>
        /// <param name="cn"></param>
        public void ExportFrequencyFile(FileExportType fet, string FileName, ChannelBase cn)
        {
            string args = TsRStartSettings(true);

            if (fet == FileExportType.HTML)
            { args += @"-H """ + FileName + @""" "; }
            else if (fet == FileExportType.XML)
            { args += @"-x """ + FileName + @""" "; }
            
            args += ParameterasHardware(cn, 0);
            LaunchTSReader(args);
        }

        /// <summary>
        /// Checks if a channel already exists in the channel list by comparing frequency and Channel number
        /// </summary>
        /// <param name="cn">check if that channel exists</param>
        /// <returns>true if channel exists, false if channel does not exist</returns>
        public bool ChannelExists(ChannelBase cn)
        {
            foreach (ChannelBase c in _currChannels)
            {
                if (c.iFrequency == cn.iFrequency && c.iProgNo == cn.iProgNo)
                { return true; }
            }
            return false;
        }

        private void LaunchTSReader(string arguments)
        {
            if (!File.Exists(cSettings.Cfg.sTsReaderExe)) return;

            Process p = new Process();
            p.StartInfo.FileName = cSettings.Cfg.sTsReaderExe;
            p.StartInfo.Arguments = arguments;
            p.Start();
        }

        private void CreateScheduledTask(string arguments, RecordingSettings rs)
        {
            StringBuilder args = new StringBuilder();
            args.Append("/create /sc ONCE /st "); // 'sc ONE' >> start task once
            args.Append(rs.dtTimedRecordingTime.ToString("HH:mm:ss")); // 'st [time]' >> Time when task starts
            args.Append(" /sd ");
            args.Append(rs.dtTimedRecordingTime.ToString("dd'/'MM'/'yyyy")); // 'sd [date]' >> Date when recording starts
            args.Append(@" /tn ""TsChanManRecording_");
            args.Append(DateTime.Now.Ticks); // 'tn [name]' >> Name of the scheduled task
            args.Append(@""" /tr ""\""");
            args.Append(cSettings.Cfg.sTsReaderExe);
            args.Append(@"\"" ");
            args.Append(arguments);
            args.Append(@""""); // 'tr [file+args]' >> File & arguments for the task to start

            Process p = new Process();
            p.StartInfo.FileName = "schtasks";
            p.StartInfo.Arguments = args.ToString();
            p.StartInfo.CreateNoWindow = true;
            p.Start();

            p.WaitForExit(2500); // Wait max 2,5 sec
        }

        /// <summary>
        /// Sets parameters for the TSReader Executable depending on the TV-Hardware used
        /// </summary>
        /// <param name="cn"></param>
        /// <param name="VlcNumber">0 if tune into frequency, 1-16 if tuning directly into channel</param>
        /// <returns></returns>
        private string ParameterasHardware(ChannelBase cn, int VlcNumber)
        {
            string sReturn = "";
            if (VlcNumber > 0) { sReturn = "-v " + cn.iProgNo + " " + VlcNumber + " "; }
            sReturn += cn.iFrequency + " ";

            object[] parames;

            if (cSettings.Cfg.cTvHardware == ChannelType.DVBS)
            {
                ChannelDVBS cs = (ChannelDVBS)cn;
                parames = new object[] { Convert.ToInt32(cs.polarisation), " ",
                                            cs.iSymbolRate, " ",
                                            cs.iLNBFrequency, " ",
                                            Convert.ToInt32(cs.bKhz), " ",
                                            cs.shSwitch };
                sReturn += string.Concat(parames);
            }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBSFull)
            {
                ChannelDVBSFull cs = (ChannelDVBSFull)cn;
                parames = new object[] { Convert.ToInt32(cs.polarisation), " ",
                                            cs.iSymbolRate, " ",
                                            cs.iLNBFrequency, " ",
                                            Convert.ToInt32(cs.bKhz), " ",
                                            Convert.ToInt32(cs.dvbsMode), " ",
                                            cs.iFec, " ",
                                            cs.shSwitch };
                sReturn += string.Concat(parames);
            }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBC)
            {
                ChannelDVBC cc = (ChannelDVBC)cn;
                parames = new object[] {    cc.iSymbolRate, " ",
                                            cc.iQAM, " ",
                                            Convert.ToInt32(cc.bInversion), " ",
                                            cc.iBandwith, " " };
                sReturn += string.Concat(parames);
            }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBT)
            {
                ChannelDVBT ct = (ChannelDVBT)cn;
                parames = new object[] {    Convert.ToInt32(ct.bInversion), " ",
                                            ct.iBandwith, " " };
                sReturn += string.Concat(parames);
            }
            return sReturn;
        }

        /// <summary>
        /// Sets start-parameters for TSReader as configured in the Options-dialog (UniProcessor, etc...)
        /// </summary>
        /// <param name="bFrequency">true if tune into frequency, fals if tuning directly into channel</param>
        private string TsRStartSettings(bool bFrequency)
        {
            string sReturn = "";

            // Only start TSReader minimized when directly launching a channel
            if (!bFrequency && cSettings.Cfg.bStartTSReaderMinimized)
            { sReturn = "-M "; }

            if (cSettings.Cfg.bUniProcessorTSReader)
            { sReturn += "-1 "; }

            if (cSettings.Cfg.bDisableTSReaderThumbnailStreamParsing)
            { sReturn += "-d "; }

            if (cSettings.Cfg.bSingleTSReaderInstance)
            { sReturn += "-i "; }

            if (!bFrequency && cSettings.Cfg.bStartTSReaderMinimizedToNtfcArea)
            { sReturn += "-Mm "; }

            if (string.IsNullOrEmpty(sReturn))
            { sReturn = " "; } // Add blank between StartSettings & Frequency-/Channel-Settings
            return sReturn;
        }

        private void LoadChannels()
        {
            string channelFile = Path.Combine(cSettings.sAppDataDir, cType.ToString() + "_channels.xml");

            // Create an empty dummy file
            if (!File.Exists(channelFile))
            { SaveChannels(); }

            _currChannels = (List<ChannelBase>)cHelper.DeserializeFromXML(_currChannels.GetType(), channelFile);
        }

        private void SaveChannels()
        {
            cHelper.SerializeToXML(_currChannels, Path.Combine(cSettings.sAppDataDir, cType.ToString() + "_channels.xml"));
        }

        /// <summary>
        /// Adds a channel into the channel list and saves it to the hard-drive
        /// </summary>
        /// <param name="cn"></param>
        public void AddChannel(ChannelBase cn)
        {
            if (cn == null || _currChannels.Contains(cn)) return;

            _currChannels.Add(cn);
            SaveChannels();
        }

        /// <summary>
        /// Removes a channel from the channel list and saves the changes to the hard drive
        /// </summary>
        /// <param name="cn"></param>
        public void RemoveChannel(ChannelBase cn)
        {
            if (_currChannels.Contains(cn))
            { _currChannels.Remove(cn); }
            SaveChannels();
        }

        /// <summary>
        /// Replaces a channel on the specific index with another channel (used to edit channel)
        /// Afterwards the new channel list gets saved to the hard drive
        /// </summary>
        /// <param name="channelIndex">Index in List 'currChannels'</param>
        /// <param name="cn"></param>
        public void ReplaceChannel(int channelIndex, ChannelBase cn)
        {
            _currChannels[channelIndex] = cn;
            SaveChannels();
        }

        /// <summary>
        /// Changes the assigned favorite number for a channel and saves the change back to the hard drive
        /// </summary>
        /// <param name="favNo"></param>
        /// <param name="cn"></param>
        public void ChangeChannelFavorite(int favNo, ChannelBase cn)
        {
            if (_currChannels.Contains(cn))
            { cn.iFavorite = favNo; }
            SaveChannels();
        }

        /// <summary>
        /// Checks if a channel-file in the old format (channel_[type].xml) is persisent in the
        /// application directory
        /// </summary>
        /// <returns></returns>
        private void CheckForOldChannels()
        {
            Assembly a = Assembly.GetExecutingAssembly();
            string chanFile = Path.GetDirectoryName(a.Location);

            if (cSettings.Cfg.cTvHardware == ChannelType.DVBS)
            { chanFile = Path.Combine(chanFile, "channel_dvbs.xml"); }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBSFull)
            { chanFile = Path.Combine(chanFile, "channel_dvbs2.xml"); }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBC)
            { chanFile = Path.Combine(chanFile, "channel_dvbc.xml"); }
            else if (cSettings.Cfg.cTvHardware == ChannelType.DVBT)
            { chanFile = Path.Combine(chanFile, "channel_dvbt.xml"); }
            else if (cSettings.Cfg.cTvHardware == ChannelType.ATSC)
            { chanFile = Path.Combine(chanFile, "channel_atsc.xml"); }
            else if (cSettings.Cfg.cTvHardware == ChannelType.SCTE)
            { chanFile = Path.Combine(chanFile, "channel_scte.xml"); }

            if (File.Exists(chanFile))
            {
                if (cSettings.Cfg.saConvertedFiles == null)
                { cSettings.Cfg.saConvertedFiles = new StringCollection(); }
                else
                { if (cSettings.Cfg.saConvertedFiles.Contains(chanFile)) return; }

                try
                { ConvertOldChannels(chanFile); }
                catch (Exception ex)
                {
                    MessageBox.Show("Could not convert old channel file to new format:" +
                                    Environment.NewLine + ex.Message);
                }
            }
        }

        private void ConvertOldChannels(string chanFile)
        {
            ChannelBase cn;
            int iError = 0, iCount = 0;

            using (XmlTextReader xread = new XmlTextReader(chanFile))
            {
                xread.WhitespaceHandling = WhitespaceHandling.Significant;
                xread.MoveToContent();

                while (xread.Read())
                {
                    try
                    {
                        if (cSettings.Cfg.cTvHardware == ChannelType.DVBS)
                        { cn = new ChannelDVBS(); }
                        else if (cSettings.Cfg.cTvHardware == ChannelType.DVBSFull)
                        { cn = new ChannelDVBSFull(); }
                        else if (cSettings.Cfg.cTvHardware == ChannelType.DVBC)
                        { cn = new ChannelDVBC(); }
                        else if (cSettings.Cfg.cTvHardware == ChannelType.DVBT)
                        { cn = new ChannelDVBT(); }
                        else
                        { cn = new ChannelBase(); }

                        // Das Lesen beenden, wenn kein Sender mehr vorhanden ist
                        if (xread.GetAttribute("name") == null) break;

                        cn.sName = xread.GetAttribute("name");
                        cn.iFrequency = int.Parse(xread.ReadElementString());

                        if (cSettings.Cfg.cTvHardware == ChannelType.DVBS || cSettings.Cfg.cTvHardware == ChannelType.DVBSFull)
                        {
                            ChannelDVBS cs = (ChannelDVBS)cn;
                            cs.iSymbolRate = int.Parse(xread.ReadElementString());
                            cs.iLNBFrequency = int.Parse(xread.ReadElementString());
                            cs.bKhz = Convert.ToBoolean(int.Parse(xread.ReadElementString()));
                            cs.polarisation = (DvbSPols)int.Parse(xread.ReadElementString());
                            cs.shSwitch = short.Parse(xread.ReadElementString());

                            if (cSettings.Cfg.cTvHardware == ChannelType.DVBSFull)
                            {
                                ChannelDVBSFull cs2 = (ChannelDVBSFull)cs;
                                cs2.dvbsMode = (DvbSModes)int.Parse(xread.ReadElementString());
                                cs2.iFec = int.Parse(xread.ReadElementString());
                            }
                        }
                        else if (cSettings.Cfg.cTvHardware == ChannelType.DVBC)
                        {
                            ChannelDVBC cc = (ChannelDVBC)cn;
                            cc.iSymbolRate = int.Parse(xread.ReadElementString());
                            cc.iQAM = int.Parse(xread.ReadElementString());
                            cc.bInversion = Convert.ToBoolean(int.Parse(xread.ReadElementString()));
                            cc.iBandwith = int.Parse(xread.ReadElementString());
                        }
                        else if (cSettings.Cfg.cTvHardware == ChannelType.DVBT)
                        {
                            ChannelDVBT ct = (ChannelDVBT)cn;
                            ct.bInversion = Convert.ToBoolean(int.Parse(xread.ReadElementString()));
                            ct.iBandwith = int.Parse(xread.ReadElementString());
                        }
                        string LogoFile = Path.GetDirectoryName(chanFile);
                        LogoFile = Path.Combine(LogoFile, @"chanicons\" + xread.ReadElementString());
                        if (File.Exists(LogoFile))
                        { cn.bLogo = new Bitmap(LogoFile); }
                        cn.iProgNo = int.Parse(xread.ReadElementString());
                        cn.iFavorite = int.Parse(xread.ReadElementString());

                        AddChannel(cn);
                    }
                    catch (Exception)
                    { iError++; } // Just log how many channels could  N O T  be converted
                    iCount++;
                }
            }

            try
            { File.Move(chanFile, chanFile + "_old"); } // Create backup from old channel-file
            catch (Exception)
            {
                // If the application has previously been installed in %Program Files%
                // Vista's user account protection does not allow to move the file (no access rights)
                //  >> That's the reason for saving the names of all converted files 
                //      -- so they are not being converted again
                cSettings.Cfg.saConvertedFiles.Add(chanFile);
                cSettings.SaveCfg();
            }

            SaveChannels();
            MessageBox.Show(iCount - iError + " of " + iCount + " channels from old channel-file successfully converted into new file format.",
                            "Channel conversion", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }

    [System.Xml.Serialization.XmlInclude(typeof(ChannelDVBS))]
    [System.Xml.Serialization.XmlInclude(typeof(ChannelDVBSFull))]
    [System.Xml.Serialization.XmlInclude(typeof(ChannelDVBC))]
    [System.Xml.Serialization.XmlInclude(typeof(ChannelDVBT))]
    public class ChannelBase
    {
        /// <summary>
        /// Standard-Konstruktor
        /// </summary>
        public ChannelBase()
        { }

        public ChannelBase(string ChannelName, int Frequency, int Favorite, int ProgramNo,
                                Bitmap Logo)
        {
            sName = ChannelName;
            iFrequency = Frequency;
            iFavorite = Favorite;
            iProgNo = ProgramNo;
            bLogo = Logo;
        }

        public override string ToString()
        { return sName; }

        public string sName;
        public int iFrequency;
        public int iFavorite;
        public int iProgNo;

        [System.Xml.Serialization.XmlIgnore]
        public Bitmap bLogo
        {
            get
            {
                if (rawLogo == null)
                { return new Bitmap(GetType(), "none.jpg"); }
                else
                {
                    MemoryStream ms = new MemoryStream(rawLogo, false);
                    return new Bitmap(ms);
                }
            }

            set
            {
                if (value == null) return; // Keine leeren Bitmaps speichern

                MemoryStream ms = new MemoryStream();
                value = cHelper.ResizePicture(value);
                value.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                rawLogo = ms.GetBuffer();
            }
        }

        public string VLCCommand = null;

        /// <summary>
        /// This only acts as a byte[] to get the Bitmap "bLogo"
        /// It should NOT be set from outside
        /// </summary>
        public byte[] rawLogo;
    }

    public class ChannelDVBS : ChannelBase
    {
        /// <summary>
        /// Standard-Konstruktor
        /// </summary>
        public ChannelDVBS()
        { }

        public ChannelDVBS(string ChannelName, int Frequency, int Favorite, int ProgramNo,
                                Bitmap Logo, int Symbolrate, int LNBFrequency, bool Khz,
                                DvbSPols Polarisation, short Switch)
        {
            sName = ChannelName;
            iFrequency = Frequency;
            iFavorite = Favorite;
            iProgNo = ProgramNo;
            bLogo = Logo;
            iSymbolRate = Symbolrate;
            iLNBFrequency = LNBFrequency;
            bKhz = Khz;
            polarisation = Polarisation;
            shSwitch = Switch;
        }

        public int iSymbolRate;
        public int iLNBFrequency;
        public bool bKhz;
        public DvbSPols polarisation;
        public short shSwitch;
    }

    public class ChannelDVBT : ChannelBase
    {
        /// <summary>
        /// Standard-Konstruktor
        /// </summary>
        public ChannelDVBT()
        { }

        public ChannelDVBT(string ChannelName, int Frequency, int Favorite, int ProgramNo,
                                Bitmap Logo, bool Inversion, int Bandwith)
        {
            sName = ChannelName;
            iFrequency = Frequency;
            iFavorite = Favorite;
            iProgNo = ProgramNo;
            bLogo = Logo;
            bInversion = Inversion;
            iBandwith = Bandwith;
        }

        public bool bInversion;
        public int iBandwith;
    }

    public class ChannelDVBC : ChannelBase
    {
        /// <summary>
        /// Standard-Konstruktor
        /// </summary>
        public ChannelDVBC()
        { }

        public ChannelDVBC(string ChannelName, int Frequency, int Favorite, int ProgramNo,
                                Bitmap Logo, int Symbolrate, int QAM, bool Inversion, int Bandwith)
        {
            sName = ChannelName;
            iFrequency = Frequency;
            iFavorite = Favorite;
            iProgNo = ProgramNo;
            bLogo = Logo;
            iSymbolRate = Symbolrate;
            iQAM = QAM;
            bInversion = Inversion;
            iBandwith = Bandwith;
        }

        public int iSymbolRate;
        public int iQAM;
        public bool bInversion;
        public int iBandwith;
    }

    public class ChannelDVBSFull : ChannelDVBS
    {
        /// <summary>
        /// Standard-Konstruktor
        /// </summary>
        public ChannelDVBSFull()
        { }

        public ChannelDVBSFull(string ChannelName, int Frequency, int Favorite, int ProgramNo,
                                Bitmap Logo, int Symbolrate, int LNBFrequency, bool Khz,
                                DvbSPols Polarisation, short Switch, DvbSModes DvbSMode, int FEC)
        {
            sName = ChannelName;
            iFrequency = Frequency;
            iFavorite = Favorite;
            iProgNo = ProgramNo;
            bLogo = Logo;
            iSymbolRate = Symbolrate;
            iLNBFrequency = LNBFrequency;
            bKhz = Khz;
            polarisation = Polarisation;
            shSwitch = Switch;
            dvbsMode = DvbSMode;
            iFec = FEC;
        }

        public DvbSModes dvbsMode;
        public int iFec;
    }
}
